﻿#define _CRT_SECURE_NO_WARNINGS 1

#include "CentralCache.hpp"
#include "PageCache.hpp"


 // C++ 中，​静态成员变量​（Static Member Variable）必须在两个地方声明：
 // 1. 在类的定义中声明为 static。
 // 2. 在类的外部进行定义和初始化。为静态成员分配实际内存空间

CentralCache CentralCache::_sInst;

// 获取一个非空的 span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size) {
	// 查看当前的 spanlist 中是否有空闲的 span
	Span* it = list.Begin();
	while (it != list.End()) {
		// 这里的 _freeList 是指 span 中的自由链表头指针，_freeList != nullptr 说明这个 span 中有未分配的小块内存
		if(it ->_freeList != nullptr)
			return it;
		it = it->_next;
	}

	// 如果没有空闲的 span，则需要申请一个新的 span
	// 只能找 page cache 要
		// 虽然此时central cache的这个桶当中是没有内存供其他thread cache申请的，
		// 但thread cache除了申请内存还会释放内存，
		// 如果在访问 page cache 前将central cache对应的桶锁解掉，
		// 那么此时当其他thread cache想要归还内存到central cache的这个桶时就不会被阻塞
	list._mtx.unlock();
 
	PageCache::GetInstance()->_pageMtx.lock();
	Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
	span->_isused = true;
	span->_objectSize = size;
	PageCache::GetInstance()->_pageMtx.unlock();

	// 接下来是对刚刚获取的 span 进行切分，
	// 这会这个 span 还没有挂到桶上，其他线程访问不到，所以不需要加锁

	// 这里的 span 是一个大块内存，_PAGEID 是页号，_n 是页数
	// 这里 start 和 end 的类型必须要用 char* 来表示，因为我们要对内存进行字节级别的操作
	// void* 不能进行算术运算，因为 void 没有大小类型，编译器无法进行步长判断
	// 也不能用 int* 或者 其他的指针
	// 因为是字节级的操作，只有 char* +1 也是跳转1个字节，但是 int* 就是跳转4个字节
	char* start = (char*)(span->_PAGEID << PAGE_SHIFT);
	size_t bytes = span->_n << PAGE_SHIFT;
	char* end = start + bytes;

	// 把大块内存切成自由链表连接起来
	// 1. 先切一块下来去做头，方便尾插
	span->_freeList = start;
	start += size;
	void* tail = span->_freeList;
	int i = 1;
	while (start < end) {
		++i;
		NextObj(tail) = start;
		tail = NextObj(tail);
		start += size;
	}
	NextObj(tail) = nullptr; // 最后一个对象的下一个指针置空

	// 检查是否有n个对象
	//int j = 0;
	//void* cur = span->_freeList;
	//while (cur) {
	//	cur = NextObj(cur);
	//	++j;
	//}
	//if (j != bytes / size) {
	//	// 条件断点
	//	int x = 0; 
	//}

	// 2. 切好 span 后，需要把 span 挂到桶里面去，得加锁
	list._mtx.lock();
	list.PushFront(span);
	
	return span;
}

// 从 Central cache 获取一定数量的对象给 thread cache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size) {
	size_t index = SizeClass::Index(size);
	
	// 由于central cache是所有线程共享的，所以我们在访问central cache中的哈希桶时，需要先给对应的哈希桶加上桶锁，在获取到对象后再将桶锁解除。
	_spanLists[index]._mtx.lock();

	Span* span = GetOneSpan(_spanLists[index], size);
	assert(span);
	assert(span->_freeList);

	// 从 span 中获取 batchNum个对象
	// 如果不够 batchNum 个，有多少拿多少
	start = span->_freeList;
	end = start;
	size_t i = 0;
	size_t actualNum = 1;

	// 在向central cache获取对象时，先是在central cache对应的哈希桶中获取到一个非空的span对象，
	// 然后从这个sapn的自由链表中取出n个指定大小的对象，但是可能这个非空span的自由链表当中对象不足n个，
	// 这时该自由链表当中有多少个对象给多少即可。
	while (i < batchNum - 1 && NextObj(end) != nullptr) {
		end = NextObj(end);
		++i;
		++actualNum;
	}
	span->_freeList = NextObj(end);
	NextObj(end) = nullptr;
	span->_userCount += actualNum;

	// 检查是否有n个对象
	int j = 0;
	void* cur = start;
	while (cur) {
		cur = NextObj(cur);
		++j;
	}
	if (j != actualNum) {
		// 条件断点
		int x = 0;
	}

	_spanLists[index]._mtx.unlock();

	return actualNum;
}  


// 将⼀定数量的对象释放到span跨度
void CentralCache::ReleaseListToSpans(void* start, size_t size) {
	size_t index = SizeClass::Index(size);
	_spanLists[index]._mtx.lock();

	while (start) {
		 void* next = NextObj(start); // 记录下一个节点

		 // 获取从对象到 span 的映射
		 Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
		 // 将对象头插到 span 的自由链表中
		 NextObj(start) = span->_freeList;
		 span->_freeList = start;

		 // 更新被分配给 thread cache的对象数量
		 span->_userCount--;

		 // 当这个span分配出去的对象数量为0时，说明这个span已经没有被分配出去的对象了，
		 if (span->_userCount == 0) {
			 // 此时这个span就可以再回收给 page cache 了
			 _spanLists[index].Erase(span);
			 span->_freeList = nullptr; // 自由链表制空
			 span->_next = nullptr;
			 span->_prev = nullptr;

			 // 释放 span 给 page cache 时，使用 page cache 的锁即可，这是把 central cache桶锁解掉
			 _spanLists[index]._mtx.unlock(); // 解除桶锁
			 PageCache::GetInstance()->_pageMtx.lock(); // 加锁
			 PageCache::GetInstance()->ReleaseSpanToPageCache(span); 
			 PageCache::GetInstance()->_pageMtx.unlock(); // 解锁
			 _spanLists[index]._mtx.lock(); // 加桶锁
		 }
		 start = next;
	}

	_spanLists[index]._mtx.unlock();
}